using System;
using System.Collections.Generic;
using System.IO;

using AGG;
using AGG.Transform;
using AGG.Image;
using AGG.VertexSource;
using AGG.UI;
using AGG.Font;

/*
 * TODO:
 *  show the z height for the visible layer 
 *  show the temperature for the layer (if more than one figure something out)
 *  show the x y z for the vertex under the pointer
 *  put in a verticle zoom control (google maps is the model)
 *  put the open gcode in a file menu (good reason to write a menu widget :) )
 *  make soom zoom maintain what is in the current center of the screen
 * 
 * DONE:
 *  mouse wheel to zoom
 *  use the mouse to move the model
 *  make a true model and view for the gcode
 *  remember last directory next time opening file dialog
*/

namespace Fabinator
{
    public abstract class LayoutWidget : GUIWidget
    {
        public enum AlignmentPositions { None, West, NothWest, North, NothEast, East, SouthEast, South, SouthWest, Centered };
        AlignmentPositions alignment;

        public AlignmentPositions Alignment
        {
            get
            {
                return alignment;
            }
        }

        public LayoutWidget(AlignmentPositions alignment)
        {
            this.alignment = alignment;
            CanFocus = false;
        }

        public override void OnParentChanged()
        {
            Parrent.Resize += new ResizeEventHandler(Parrent_Resize);
            base.OnParentChanged();
        }

        public abstract void Parrent_Resize(object sender, EventArgs e);
    }

    public class InOrderLayoutWidget : LayoutWidget
    {
        double spaceBetweenChildren = 5;
        double widthOfAllChildren = 0;
        double heightOfAllChildren = 0;
        double biggestHeight = 0;
        double biggestWidth = 0;
        bool isHorizontal = true;

        public InOrderLayoutWidget(AlignmentPositions alignment)
            : base(alignment)
        {
        }

        void ChildrenChanged()
        {
            widthOfAllChildren = 0;
            heightOfAllChildren = 0;
            biggestHeight = 0;
            biggestWidth = 0;

            for(int childIndex = 0; childIndex < m_Children.Count; childIndex++)
            {
                GUIWidget child = m_Children[childIndex];

                widthOfAllChildren += child.Bounds.Width;
                biggestWidth = Math.Max(biggestWidth, child.Bounds.Width);
                heightOfAllChildren += child.Bounds.Height;
                biggestHeight = Math.Max(biggestHeight, child.Bounds.Height);

                if (childIndex + 1 < m_Children.Count)
                {
                    if (isHorizontal)
                    {
                        widthOfAllChildren += spaceBetweenChildren;
                    }
                    else
                    {
                        heightOfAllChildren += spaceBetweenChildren;
                    }
                }
            }
        }

        public override void Parrent_Resize(object sender, EventArgs e)
        {
            Bounds = Parrent.Bounds;

            ChildrenChanged(); // TODO: only do this when they actually change

            Vector2D currentOffset = new Vector2D(0, 0);
            double curX;
            switch (Alignment)
            {
                case AlignmentPositions.North:
                    curX = 0;
                    foreach (GUIWidget child in m_Children)
                    {
                        double newY = Parrent.Bounds.Top - biggestHeight;
                        child.Location = new Vector2D(curX - child.Bounds.Left, newY - child.Bounds.Bottom);
                        curX += child.Bounds.Width + spaceBetweenChildren;
                    }
                    break;

                case AlignmentPositions.Centered:
                    curX = Parrent.Bounds.Width / 2 - widthOfAllChildren / 2;
                    foreach (GUIWidget child in m_Children)
                    {
                        double newY = Parrent.Bounds.Top / 2 - child.Bounds.Height / 2;
                        child.Location = new Vector2D(curX - child.Bounds.Left, newY - child.Bounds.Bottom);
                        curX += child.Bounds.Width + spaceBetweenChildren;
                    }
                    break;
            }
        }
    }

    public class NumTextEditWidget : TextEditWidget
    {
        public NumTextEditWidget(string text, int pixelWidth, int pixelHeight, double capitalHeight)
            : base(text, pixelWidth, pixelHeight, capitalHeight)
        {
        }

        public override void OnKeyDown(KeyEventArgs keyEvent)
        {
            switch (keyEvent.KeyCode)
            {
                case Keys.Up:
                    keyEvent.SuppressKeyPress = true;
                    keyEvent.Handled = true;
                    Text = (System.Int32.Parse(Text) + 1).ToString();
                    OnTextChanged();
                    break;

                case Keys.Down:
                    keyEvent.SuppressKeyPress = true;
                    keyEvent.Handled = true;
                    Text = (System.Int32.Parse(Text) - 1).ToString();
                    OnTextChanged();
                    break;
            }

            base.OnKeyDown(keyEvent);
        }

        public override void  OnKeyPress(KeyPressEventArgs keyPressEvent)
        {
            if(Char.IsDigit(keyPressEvent.KeyChar))
            {
                base.OnKeyPress(keyPressEvent);
            }
        }
    }

    public class Fabinator_application : MatchParentBoundsWidget
    {
        double layerScale = 1;
        int layerIndex;
        Affine scallingTransform;
        
        Vector2D unscaledRenderOffset = new Vector2D(0, 0);
        
        Vector2D lastMousePosition = new Vector2D(0, 0);
        
        GCode loadedGCode;
        VertexSourceGCodeView gCodeView;

        ButtonWidget openFileButton;
        ButtonWidget exportFileButton;
        ButtonWidget smoothButton;
        TextWidget layerCountTextWidget;
        NumTextEditWidget currentLayerIndex;
        ButtonWidget prevLayerButton;
        ButtonWidget nextLayerButton;

        //WindowWidget windowTest;

        GuiHalSurface guiSurface;
		
        public Fabinator_application(GuiHalSurface guiSurface)
        {
            this.guiSurface = guiSurface;
            InOrderLayoutWidget keepOnTop = new InOrderLayoutWidget(LayoutWidget.AlignmentPositions.North);
            AddChild(keepOnTop);

            prevLayerButton = new ButtonWidget(0, 0, "<<");
            prevLayerButton.Click += new ButtonWidget.ButtonEventHandler(prevLayer_ButtonClick);
            keepOnTop.AddChild(prevLayerButton);

            currentLayerIndex = new NumTextEditWidget("1", 45, 20, 12);
            currentLayerIndex.Multiline = false;
            keepOnTop.AddChild(currentLayerIndex);
            currentLayerIndex.TextChanged += new TextEditWidget.TextChangedEventHandler(layerCountTextWidget_TextChanged);
            currentLayerIndex.Focus();

            layerCountTextWidget = new TextWidget("/1____", 12);
            keepOnTop.AddChild(layerCountTextWidget);

            nextLayerButton = new ButtonWidget(0, 0, ">>");
            nextLayerButton.Click += new ButtonWidget.ButtonEventHandler(nextLayer_ButtonClick);
            keepOnTop.AddChild(nextLayerButton);

            openFileButton = new ButtonWidget(0, 0, "Open GCode");
            openFileButton.Click += new ButtonWidget.ButtonEventHandler(openFileButton_ButtonClick);
            keepOnTop.AddChild(openFileButton);

            exportFileButton = new ButtonWidget(0, 0, "Export");
            exportFileButton.Click += new ButtonWidget.ButtonEventHandler(exportFileButton_Click);
            keepOnTop.AddChild(exportFileButton);

            smoothButton = new ButtonWidget(0, 0, "Smooth");
            smoothButton.Click += new ButtonWidget.ButtonEventHandler(smoothButton_Click);
            keepOnTop.AddChild(smoothButton);

            //windowTest = new WindowWidget(new rect_d(5, 40, 200, 200));
            //AddChild(windowTest);
        }

        void exportFileButton_Click(object sender, MouseEventArgs mouseEvent)
        {
            FileStream file = new FileStream("coordinates.txt", FileMode.Create, FileAccess.Write);
            StreamWriter sw = new StreamWriter(file);

            string fileString = "";
            int numVerts = loadedGCode.GCodeVertices.Count;
            for (int i = 0; i < numVerts; i++)
            {
                Vector2D cur = new Vector2D(loadedGCode.GCodeVertices[i].X, loadedGCode.GCodeVertices[i].Y);
                if(loadedGCode.GCodeVertices[i].ExtrudeOn)
                {
                    fileString += "Table.MoveToExitSpeed(";
                }
                else
                {
                    fileString += "Table.MoveTo(";
                }

                fileString += ((int)cur.x).ToString() + ", " + ((int)cur.y).ToString() + ", 50)\r\n";
            }

            sw.Write(fileString);
            sw.Close();
            file.Close();
        }

        void smoothButton_Click(object sender, MouseEventArgs mouseEvent)
        {
            int numVerts = loadedGCode.GCodeVertices.Count;
            for (int i = 0; i < numVerts; i++)
            {
                if (i > 0 && i < numVerts - 1)
                {
                    if (loadedGCode.GCodeVertices[i].ExtrudeOn
                        && loadedGCode.GCodeVertices[i - 1].ExtrudeOn
                        && loadedGCode.GCodeVertices[i + 1].ExtrudeOn)
                    {
                        Vector2D cur = new Vector2D(loadedGCode.GCodeVertices[i].X, loadedGCode.GCodeVertices[i].Y);
                        Vector2D prev = new Vector2D(loadedGCode.GCodeVertices[i - 1].X, loadedGCode.GCodeVertices[i - 1].Y);
                        Vector2D next = new Vector2D(loadedGCode.GCodeVertices[i + 1].X, loadedGCode.GCodeVertices[i + 1].Y);
                        //double angle = (prev - cur).GetDeltaAngle(next - cur); // this is not what we want but we do want somithing to prevent the lions foot from loosing it's sharp corners
                        //if (angle > Math.PI / 4 * 3 && angle < Math.PI / 4 * 5)
                        {
                            double x = (prev.x + next.x) / 2;
                            double y = (prev.y + next.y) / 2;
                            loadedGCode.GCodeVertices[i].X = x;
                            loadedGCode.GCodeVertices[i].Y = y;
                        }
                    }
                }
            }

            Invalidate();
        }

        void SetActiveLayer(int layer)
		{
			layerIndex = layer;
			
			if (gCodeView == null || layerIndex < 0)
            {
                layerIndex = 0;
            }
            else if(layerIndex >= gCodeView.NumLayers)
            {
				layerIndex = gCodeView.NumLayers-1;
            }

            currentLayerIndex.Text = (layerIndex + 1).ToString();

            Invalidate();
		}

        void layerCountTextWidget_TextChanged(object sender, EventArgs e)
        {
            SetActiveLayer(System.Int32.Parse(currentLayerIndex.Text)-1);
        }

        void nextLayer_ButtonClick(object sender, MouseEventArgs mouseEvent)
        {
            SetActiveLayer(layerIndex+1);
        }

        void prevLayer_ButtonClick(object sender, MouseEventArgs mouseEvent)
        {
            SetActiveLayer(layerIndex-1);
        }

        void openFileButton_ButtonClick(object sender, MouseEventArgs mouseEvent)
        {
            GuiHalSurface.OpenFileDialogParams openParams = new GuiHalSurface.OpenFileDialogParams("gcode files (*.gcode)", "*.*");
            Stream streamToLoadFrom = guiSurface.OpenFileDialog(openParams);
            if (streamToLoadFrom != null)
            {
                loadedGCode = new GCode();
                loadedGCode.LoadGCode(streamToLoadFrom);
                layerIndex = 0;
                currentLayerIndex.Text = (layerIndex + 1).ToString();
                gCodeView = new VertexSourceGCodeView(loadedGCode);
                rect_d bounds = gCodeView.GetBounds();

                unscaledRenderOffset.x = -(bounds.Left + bounds.Width / 2);
                unscaledRenderOffset.y = -(bounds.Bottom + bounds.Height / 2);
                layerScale = Width / bounds.Width;
            }

            Invalidate();
        }

        void layerSlider_ValueChanged(object sender, EventArgs e)
        {
            Invalidate();
        }

        public override void OnDraw(Graphics2D graphics2D)
        {
            this.NewGraphics2D().Clear(new RGBA_Bytes(255, 255, 255));

            if (loadedGCode != null)
            {
                layerCountTextWidget.Text = "/" + gCodeView.NumLayers.ToString();

                Affine transform;
                transform = Affine.NewIdentity();
                transform *= Affine.NewTranslation(unscaledRenderOffset);
                
                // scale to view 
                scallingTransform = Affine.NewScaling(layerScale, layerScale);

                transform *= scallingTransform;
                transform *= Affine.NewTranslation(Width / 2, Height / 2);

                // This code renders the layer:
                VertexSourceApplyTransform transformedPathStorage = new VertexSourceApplyTransform(gCodeView, transform);
                conv_stroke stroke = new conv_stroke(transformedPathStorage);
                graphics2D.Render(stroke, layerIndex, RGBA_Bytes.Black);

                //windowTest.NewGraphics2D().Render(stroke, layerIndex, new RGBA_Bytes(0, 0, 0));
            }

            //LiberationSansFont.Instance.ShowDebugInfo(graphics2D);

            try
            {
                base.OnDraw(graphics2D);
            }
            catch (Exception e)
            {
                Console.WriteLine("{0} Exception caught.", e);
            }
        }

        public override void OnMouseDown(MouseEventArgs mouseEvent)
        {
        	base.OnMouseDown(mouseEvent);
        	if (MouseCaptured)
        	{
        		lastMousePosition.x = mouseEvent.X;
        		lastMousePosition.y = mouseEvent.Y;
        	}
        }

        public override void OnMouseWheel(MouseEventArgs mouseEvent)
        {
            base.OnMouseWheel(mouseEvent);
            if (MouseOverWidget) // TODO: find a good way to decide if you are what the wheel is trying to do
            {
                const double deltaFor1Click = 120;
                layerScale = layerScale + layerScale * (mouseEvent.WheelDelta / deltaFor1Click) * .1;
                Invalidate();
            }
        }

        public override void OnMouseMove(MouseEventArgs mouseEvent)
        {
        	base.OnMouseMove(mouseEvent);
        	if (MouseCaptured)
        	{
        		Vector2D mousePos = new Vector2D(mouseEvent.X, mouseEvent.Y);
        		Vector2D mouseDelta = mousePos - lastMousePosition;
        		lastMousePosition = mousePos;
        		
        		scallingTransform.inverse_transform(ref mouseDelta);
        		
        		unscaledRenderOffset += mouseDelta;
        		
        		Invalidate();
        	}
        }

        public static void StartDemo(bool useHWAcceleration)
        {
#if true
            GuiHalFactory.SetGuiBackend("WindowsFormsBitmapBackendGuiFactory");
            GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(512, 400, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgr24);
#else
            GuiHalFactory.SetGuiBackend(new WindowsFormsOpenGLBackendGuiFactory());
            GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(512, 400, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgra32);
#endif

            primaryWindow.Caption = "Fabinator - GCode Visualizer";

            primaryWindow.AddChild(new Fabinator_application(primaryWindow));
            primaryWindow.Run();
        }

        [STAThread]
        public static void Main(string[] args)
        {
        	StartDemo(false);
        }
    }
}
